#include <linux/proc_fs.h>
#include <linux/device.h>
#include <linux/usb/composite.h>
#include <linux/uaccess.h>
#include "gadget_chips.h"

#define BUFLEN 4096
static struct 
{
	struct semaphore limit_sem;
	char buffer[BUFLEN];
	int start;
	int end;
	struct usb_ctrlrequest *ctrlreq;
} ringbuffer;
static char ep0_ctrl_data[8];
static struct usb_request *ep0_read_ctrl_data_req;
static struct usb_composite_dev *c_dev; // we should not use this global var. 

static inline int next(int i) { return (i+8)%BUFLEN; }

static void addtobuffer(void)
{
	if( next(ringbuffer.end) != ringbuffer.start )
	  {
#if 0
		/* debug message */
		int i;
		printk("<3>");
		for(i=0;i<8;i++) printk("%02x", ep0_ctrl_data[i]);
		printk("\n");
#endif
		
		memcpy( ringbuffer.buffer + ringbuffer.end, ep0_ctrl_data, 8 );
		ringbuffer.end = next(ringbuffer.end);
		up(&ringbuffer.limit_sem); // proc_read can continue.
	  }
}

static void ep0_ctrl_data_read_complete(struct usb_ep *ep, struct usb_request *req)
{
	int i;
	if(req->status==0)
	  {
		for(i=0;i<min(8u,req->actual);i++)
			ep0_ctrl_data[i] = ((char*)req->buf)[i];
		while(i<8)
			ep0_ctrl_data[i++] = 0;
		addtobuffer();
	  }
	else
		printk("<3>ep0 complete fail.\n");
}

/* this function is in atomic context */
static int pretreat(struct usb_gadget *gadget, const struct usb_ctrlrequest *ctrl)
{
	if( ctrl->bRequestType!=0x21 || ctrl->bRequest!=9 || le16_to_cpu(ctrl->wValue)!=0x200 || 
	  le16_to_cpu(ctrl->wIndex)!=0 || le16_to_cpu(ctrl->wLength)!=8 )
		return -1;
	ringbuffer.ctrlreq = (struct usb_ctrlrequest *)ctrl;
	return usb_ep_queue(gadget->ep0, ep0_read_ctrl_data_req, GFP_ATOMIC);
}

#include "composite.c"
#include "usbstring.c"
#include "config.c"
#include "epautoconf.c"
#include "int_config.c"

#define DRIVER_VENDOR_NUM	0x08F7
#define DRIVER_PRODUCT_NUM	0x0002

static struct usb_device_descriptor device_desc = {
	.bLength =		sizeof device_desc,
	.bDescriptorType =	USB_DT_DEVICE,

	.bcdUSB =		__constant_cpu_to_le16(0x0200),
	.bDeviceClass =		USB_CLASS_VENDOR_SPEC,

	.idVendor =		__constant_cpu_to_le16(DRIVER_VENDOR_NUM),
	.idProduct =		__constant_cpu_to_le16(DRIVER_PRODUCT_NUM),
	.bNumConfigurations =	1,
};

#define STRING_MANUFACTURER_IDX		0
#define STRING_PRODUCT_IDX		1

static struct usb_string strings_dev[] = {
	[STRING_MANUFACTURER_IDX].s = "Shanghai Jiao Tong University",
	[STRING_PRODUCT_IDX].s = "Emulator for Vernier Temperature Probe",
	{  }
};

static struct usb_gadget_strings stringtab_dev = {
	.language	= 0x0409,
	.strings	= strings_dev,
};

static struct usb_gadget_strings *dev_strings[] = {
	&stringtab_dev,
	NULL,
};

static int __init temprobe_bind(struct usb_composite_dev *cdev)
{
	int			gcnum;
	struct usb_gadget	*gadget = cdev->gadget;
	int			id;

	id = usb_string_id(cdev);
	if (id < 0)
		return id;
	strings_dev[STRING_MANUFACTURER_IDX].id = id;
	device_desc.iManufacturer = id;

	id = usb_string_id(cdev);
	if (id < 0)
		return id;
	strings_dev[STRING_PRODUCT_IDX].id = id;
	device_desc.iProduct = id;

	int_add(cdev);

	gcnum = usb_gadget_controller_number(gadget);
	if (gcnum >= 0)
		device_desc.bcdDevice = cpu_to_le16(0x0200 + gcnum);
	else
		device_desc.bcdDevice = __constant_cpu_to_le16(0x9999);

	ep0_read_ctrl_data_req = usb_ep_alloc_request(gadget->ep0, GFP_ATOMIC);
	if (!ep0_read_ctrl_data_req)
		return -ENOMEM;
	ep0_read_ctrl_data_req->length = 8;
	ep0_read_ctrl_data_req->buf = kmalloc(8, GFP_ATOMIC);
	ep0_read_ctrl_data_req->complete = ep0_ctrl_data_read_complete;
	if (!ep0_read_ctrl_data_req->buf) {
		usb_ep_free_request(gadget->ep0, ep0_read_ctrl_data_req);
		return -ENOMEM;
	}

	c_dev = cdev; // we should not do that.
	return 0;
}

static int temprobe_unbind(struct usb_composite_dev *cdev){
	struct usb_gadget	*gadget = cdev->gadget;
	
	kfree(ep0_read_ctrl_data_req->buf);
	usb_ep_free_request(gadget->ep0, ep0_read_ctrl_data_req);
	return 0;
}

static struct usb_composite_driver temprobe_driver = {
	.name		= "temprobe",
	.dev		= &device_desc,
	.strings	= dev_strings,
	.bind		= temprobe_bind,
	.unbind		= temprobe_unbind,
};

MODULE_LICENSE("GPL");

static int read_proc_entry(char *page, char **start, off_t offset, int count, int *eof, void *data)
{
	if(offset!=0) return 0;
	down(&ringbuffer.limit_sem);
	local_irq_disable(); // since ringbuffer.buffer is filled in atomic context, I cannot use mutex or semaphore.
	memcpy(page, ringbuffer.buffer+ringbuffer.start, 8);
	ringbuffer.start = next(ringbuffer.start);
	local_irq_enable();
	*eof=1;
	return 8;
}

static struct usb_request *alloc_ep_req(struct usb_ep *ep)
{
	struct usb_request	*req;
	req = usb_ep_alloc_request(ep, GFP_ATOMIC);
	if (req) {
		req->length = 8;
		req->buf = kmalloc(8, GFP_ATOMIC);
		if (!req->buf) {
			usb_ep_free_request(ep, req);
			req = NULL;
		}
	}
	return req;
}

static void proc_complete(struct usb_ep *ep, struct usb_request *req)
{
	//if( req->status == 0 )
	//	printk("<3>proc req complete success.\n");
	kfree(req->buf);
	usb_ep_free_request(ep, req);
}

static int write_proc_entry(struct file *file, const char __user *userbuffer, unsigned long count, void *data)
{
	char *buffer;
	
	if(c_dev==0 || count<8) // device being not ready, or writing less than 8 bytes
		return count;
	if(!(buffer = kmalloc(8, GFP_KERNEL)))
		return -ENOMEM;
	if(copy_from_user(buffer, userbuffer, 8))
		goto end;
	local_irq_disable(); // prevent c_dev->config from loss
	if(c_dev->config && c_dev->config->interface[0]) // having int_config
	  {
		struct f_int *fi = func_to_struct(c_dev->config->interface[0]);
		if(fi->in_ep && fi->in_ep->driver_data==fi)
		  {
			struct usb_request *req = alloc_ep_req(fi->in_ep);
			if(req==0)
				goto end;
			
			memcpy(req->buf, buffer, 8);
			req->complete = proc_complete;
			if ( usb_ep_queue(fi->in_ep, req, GFP_ATOMIC) )
				printk("<3>cannot queue req.\n");
		  }
	  }
	local_irq_enable();
end:
	return count;
}

static int __init init(void)
{
	struct proc_dir_entry *entry = 0;
	
	entry = create_proc_entry("temprobe", 0444, 0);
	if(entry == 0){
		return -1;
	}else{
		entry->read_proc = read_proc_entry;
		entry->write_proc = write_proc_entry;
		entry->owner = THIS_MODULE;
	}
	
	sema_init(&ringbuffer.limit_sem, 0);

	return usb_composite_register(&temprobe_driver);
}
module_init(init);

static void __exit cleanup(void)
{
	remove_proc_entry("temprobe", 0);
	usb_composite_unregister(&temprobe_driver);
}
module_exit(cleanup);
